package org.flyan.ffsm.core.sm;

import org.flyan.ffsm.core.event.Event;
import org.flyan.ffsm.core.arg.Args;
import org.flyan.ffsm.core.state.State;

import java.util.HashMap;
import java.util.Map;

/**
 * 有限状态机
 * <p>
 * 有限状态机用于描述状态以及状态的转移，此类是基于 Mealy 模型的一个简单实现。
 * Mealy 模型即： y = f(s, x) 其中，x 输入， y 输出， s 状态， f 函数。
 * 若我们将 y 定为新状态，则就是传统的审核流程，每次审核都会产生一个新的
 * 状态节点（也可能不变）。
 * <p>
 * 利用有限状态机的特性，可应用于业务系统的审核流程，各种逻辑流程的封装等...
 *
 * @param <ST> 状态标志
 * @param <ET> 事件标志
 * @author flyan
 * @version 1.0
 * @date 6/23/22
 */
public class StateMachine<ST, ET> {

    /**
     * 状态表
     */
    private final Map<ST, State<ST, ET>> stateTable = new HashMap<>();

    /**
     * 事件表
     */
    private final Map<ET, Event<ST, ET>> eventTable = new HashMap<>();

    /**
     * 获取状态是否存在
     *
     * @param stateKey
     * @return
     */
    public boolean stateExists(ST stateKey) {
        return stateKey != null && stateTable.containsKey(stateKey);
    }

    public boolean stateExists(State<ST, ET> state) {
        return state != null && stateExists(state.getStateTag());
    }

    /**
     * 注册一个状态
     *
     * @param state
     */
    public void registerState(State<ST, ET> state) {
        state.bindStateMachine(this);
        stateTable.put(state.getStateTag(), state);
    }

    /**
     * 查找一个状态通过状态标志
     *
     * @param stateKey
     * @return
     */
    public State<ST, ET> findState(ST stateKey) {
        return stateTable.get(stateKey);
    }

    /**
     * 注册一个事件到状态中
     *
     * @param stateKey
     * @param event
     */
    public void registerEvent(ST stateKey, Event<ST, ET> event) {
        State<ST, ET> state = findState(stateKey);
        if (state != null) {
            state.registerEvent(event);
            eventTable.put(event.getEventTag(), event);
        }
    }

    /**
     * 注册一个事件到状态中
     *
     * @param state
     * @param event
     */
    public void registerEvent(State<ST, ET> state, Event<ST, ET> event) {
        state.bindStateMachine(this);
        state.registerEvent(event);
        stateTable.put(state.getStateTag(), state);
        eventTable.put(event.getEventTag(), event);
    }

    /**
     * 查找事件
     *
     * @param eventTag
     * @return
     */
    public Event<ST, ET> findEvent(ET eventTag) {
        if(eventExists(eventTag)) {
            try {
                return eventTable.get(eventTag).clone();
            } catch (CloneNotSupportedException ignored) {
            }
        }
        return null;
    }

    /**
     * 发生了一个事件
     *
     * @param eventTag
     * @return
     */
    public State<ST, ET> eventHappen(ET eventTag, Args args) {
        Event<ST, ET> event = findEvent(eventTag);
        if(event == null) {
            return null;
        }

        State<ST, ET> state = findState(event.getCurrState().getStateTag());
        return state != null ? state.eventHappen(eventTag, args) : null;
    }

    public State<ST, ET> eventHappen(State<ST, ET> state, ET eventTag, Args args) {
        return stateExists(state) ? state.eventHappen(eventTag, args) : null;
    }

    /**
     * 在某状态上的发生了一个事件
     *
     * @param stateTag
     * @param eventTag
     * @return
     */
    public State<ST, ET> eventHappen(ST stateTag, ET eventTag, Args args) {
        return stateExists(stateTag) ? findState(stateTag).eventHappen(eventTag, args) : null;
    }

    public boolean eventExists(ET eventTag) {
        return eventTag != null && eventTable.containsKey(eventTag);
    }

    public boolean eventExists(Event<ST, ET> event) {
        return event != null && eventTable.containsKey(event.getEventTag());
    }

    public Map<ET, Event<ST, ET>> getEventTable() {
        return eventTable;
    }

    public Map<ST, State<ST, ET>> getStateTable() {
        return stateTable;
    }

    @Override
    public String toString() {
        return "StateMachine{" +
                "stateTable=" + stateTable +
                ", eventTable=" + eventTable +
                '}';
    }

}
